/**
 * @date      2022/01/19
 * @auther    luke_guangzhong\@qq.com
 * @file      LinkQueue.h
 * @Software  CLion
 */


#ifndef LINKQUEUE_H
#define LINKQUEUE_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <pthread.h>

/*******Macros*******/

#define OVERFLOW 1
#define LStatus int
#define OK 0
#define ERROR -1

#define LINKQUEUE_KEY 116
#define LINKQUEUE_HEAD_KEY 117
#define LINKQUEUE_SIZE sizeof(LinkQueue)
#define LINKQUEUE_NODE_SIZE sizeof(QNode)

#ifndef DEBUG
#define LinkQueueDebug
#else
#define LinkQueueDebug(format, args...) printf("[LQ_DEBUG][%s][%d]:"format"\n",__FUNCTION__,__LINE__, ##args);fflush(stdout);
#endif

#ifndef LOG
#define LinkQueueLOG
#else
#define LinkQueueLOG(format, args...) printf("[LQ_LOG][%s][%d]:"format"\n",__FUNCTION__,__LINE__, ##args);fflush(stdout);
#endif

/*******Struct*******/
typedef int QNodeID;
typedef int QueueID;

typedef struct QData {
    int data;
} QData;

typedef struct QNode {
    QData data;
    QNodeID next;
} QNode, *QNodePtr;

typedef struct LinkQueue {
    QNodeID head;
    QNodeID tail;
    int length;
    pthread_mutex_t mutex;
} LinkQueue;


/*******Data Domain Functions*******/

/**
 * @brief Compares whether the data fields of two QNodes (QData) are consistent.
 * @param[in] elem.
 * @param[in] elem2.
 * @return 0 means they're the same, 1 means they're different.
 */
static LStatus QDataCmp(QData elem, QData elem2) {
    if (elem.data == elem2.data) {
        LinkQueueDebug("QData cmp match");
        return OK;
    } else {
        LinkQueueDebug("QData cmp doesn't match");
        return ERROR;
    }
}

/**
 * @brief Prints the data fields in QNode (QData).
 * @param[in] elem QData in the QNode.
 * @return 0 indicates successful execution.
 */
static LStatus printQData(QData elem) {
    printf("%d\t", elem.data);
    fflush(stdout);
    return OK;
}

/*******Queue Functions*******/

/**
 * @brief Create a share memory for a Link Queue.
 * @param [out] shmidPtr A pointer pointed to share memory id. The value which is pointed is recommended to be NULL.
 * @param [in] key A key value to create share memory.
 * @return A pointer pointed to a Link Queue. NULL means a error has occurred.
 * @note shmidptr must point to a real and already initialized variable, or SIGSEGV will be triggered.
 */
static LinkQueue *createShm_Queue(QueueID *shmidPtr, key_t key) {
    if (!shmidPtr) {
        return NULL;
    }
    LinkQueue *shmaddr = NULL;
    long page_size = sysconf(_SC_PAGESIZE);
    int data_size = (LINKQUEUE_SIZE + page_size - 1) & (~(page_size - 1));

    // create shared memory
    *shmidPtr = shmget((key_t) key, data_size, 0666 | IPC_CREAT | IPC_EXCL);
    if (*shmidPtr == -1) {
        LinkQueueDebug("shmget failed\n");
        return NULL;
    }
    // attach shared memory
    shmaddr = shmat(*shmidPtr, NULL, 0);
    if (shmaddr == (void *) -1) {
        LinkQueueDebug("shmat failed\n");
        return NULL;
    }

    shmaddr->length = -1;
    shmaddr->head = shmaddr->tail = 0;

    // return the sharememory address
    return shmaddr;
}

/**
 * @brief Get a share memory address based on share memory id.
 * @param [out] shmidPtr A pointer pointed share memory id.
 * @param [in] key A key value used to get share memory.
 * @return A pointer pointed to a Link Queue.
 * @note shmidptr must point to a real and already initialized variable, or SIGSEGV will be triggered.
 */
static LinkQueue *getShm_Queue(QueueID *shmidPtr, key_t key) {
    if (!shmidPtr) {
        return NULL;
    }
    LinkQueue *shmaddr = NULL;
    long page_size = sysconf(_SC_PAGESIZE);
    int data_size = (LINKQUEUE_SIZE + page_size - 1) & (~(page_size - 1));

    // create shared memory
    *shmidPtr = shmget((key_t) key, data_size, 0);
    if (*shmidPtr == -1) {
        LinkQueueDebug("shmget failed\n");
        return NULL;
    }

    // attach share memory
    shmaddr = shmat(*shmidPtr, NULL, 0);
    if (shmaddr == (void *) -1) {
        LinkQueueDebug("shmat failed\n");
        return NULL;
    }

    // return share memory address
    return shmaddr;
}

/**
 * @brief Detach a share memory address based on share memory id.
 * @param [in] Q A pointer pointed to a link Q.
 * @return -1 means execution failed. 0 indicates successful execution.
 */
static LStatus detShm_Queue(LinkQueue *Q) {
    if (shmdt(Q) == -1) {
        LinkQueueDebug("shmdt failed\n");
        return ERROR;
    }
    return OK;
}

/**
 * @brief Release the share memory based on share memory id.
 * @param [in] shmid Share memory id used to delete share memory.
 * @return -1 means execution failed. 0 indicates successful execution.
 */
static LStatus relShm(int shmid) {
    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        return ERROR;
    }
    return OK;
}

/*******External Functions*******/

/**
 * @brief Initialize the linked queue. Create an empty linked queue with a head node.
 * @param[in] Q A pointer pointed to a link queue.
 * @return -1 means execution failed. 0 indicates successful execution.
 * @note Please make sure the pointer is valid. Otherwise unexpected error will occur.
 */
LStatus initQueue(LinkQueue *Q);

/**
 * @brief Destroy a link queue.
 * @param[in] QPtr A pointer to link queue structure.
 * @return -1 means execution failed. 0 indicates successful execution.
 * @note Please make sure the params is valid. Otherwise unexpected error will occur.
 */
LStatus destroyQueue(LinkQueue **QPtr, QueueID Qid);

/**
 * @brief Clear a link queue.
 * @param[in] Q A pointer to link queue structure.
 * @return -1 means execution failed. 0 indicates successful execution.
 * @note This function will change the link queue's share memory id. Old share memory id will be invalid!
 */
LStatus clearQueue(LinkQueue *Q, QueueID *Qid);

/**
 * @brief Determine whether a link queue is empty.
 * @param[in] Q A pointer pointed to link queue.
 * @return true means empty. false means not empty.
 */
bool QueueEmpty(LinkQueue *Q);

/**
 * @brief Gets the number of nodes in the link queue.
 * @param[in] Q A pointer pointed to a link queue.
 * @return The number of nodes.
 */
int QueueLength(LinkQueue *Q);

/**
 * @brief Inserts an element at the end of the link queue.
 * @param[in] Q A pointer pointed to a link queue.
 * @param[in] elem The data domain which will be inserted into the link queue.
 * @return -1 means execution failed. 0 indicates successful execution.
 */
LStatus enQueue(LinkQueue *Q, QData elem);

/**
 * @brief Delete the first node in the link queue.
 * @param[in] Q A pointer pointed to a link queue.
 * @param[out] elemPtr A pointer pointed to QData structure used to save deleted node's data.
 * @return -1 means execution failed. 0 indicates successful execution.
 */
LStatus deQueue(LinkQueue *Q, QData *elemPtr);

/**
 * @param[in] Q A pointer to link queue structure.
 * @param[in] visit A function which must have no param to visit Node.
 * @return -1 means execution failed. 0 indicates successful execution.
 */
int QueueTraverse(LinkQueue *Q, void(*visit)(QNodePtr));

int random_int();

/*******Custom Functions*******/

/**
 * @brief Removes the specified node from the link queue
 * @param[in] Q A pointer to link queue structure.
 * @param[in] elem The value of the node you want to delete
 * @return -1 means execution failed. 0 indicates successful execution.
 */
LStatus rmQNode(LinkQueue *Q, QData elem);

/**
 * @brief Prints the data fields of all nodes in the link queue
 * @param[in] Q A pointer to link queue structure.
 * @return -1 means execution failed. 0 indicates successful execution.
 */
LStatus printQueue(LinkQueue *Q);

#endif //LINKQUEUE_SHM_VER_LINKQUEUE_H
